Lab 4

Computer Architecture I ShanghaiTech University

Goals

These exercises are intended to give you more practice with

Exercises

Exercise 1: calling convention checker

This exercise uses the file ex1.s.

A quick recap of RISC-V calling conventions: all functions that overwrite registers that are preserved by convention must have a prologue, which saves those register values to the stack at the start of the function, and an epilogue, which restores those values for the function's caller. You can find a more detailed explanation along with some concrete examples in these notes.

Bugs due to calling convention violations can often be difficult to find manually, so Venus provides a way to automatically report some of these errors at runtime.

Take a look at the contents of the ex1.s file, particularly at the main, simple_fn, naive_pow, inc_arr, and helper_fn functions. Enable the CC checker in Venus-Settings-Calling Convention, then run the program in the simulator. You should see an output similar to the following:

[CC Violation]: (PC=0x00000080) Usage of unset register t0! editor.S:58 mv a0, t0
[CC Violation]: (PC=0x0000008C) Setting of a saved register (s0) which has not been saved! editor.S:80 li s0, 1
[CC Violation]: (PC=0x00000094) Setting of a saved register (s0) which has not been saved! editor.S:83 mul s0, s0, a0
......

Find the source of each of the errors reported by the CC checker and fix it. You can find a list of CC error messages, as well as their meanings, in the Venus reference.

Once you've fixed all the violations reported by the CC checker, the code might still fail: this is likely because there's still some remaining calling convention errors that Venus doesn't report. Since function calls in assembly language are ultimately just jumps, Venus can't report these violations without more information, at risk of producing false positives.

The fixes for all of these errors (both the ones reported by the CC checker and the ones it can't find) should be added near the lines marked by the FIXME comments in the starter code.

Note: Venus's calling convention checker will not report all calling convention bugs; it is intended to be used primarily as a sanity check. Most importantly, it will only look for bugs in functions that are exported with the .globl directive - the meaning of .globl is explained in more detail in the Venus reference.

Action Item

Resolve all the calling convention errors in ex1.s, and be able to answer the following questions:

Testing

After fixing the errors with FIXME in ex1.s, run Venus locally with the command from the beginning of this exercise to make sure the behavior of the functions hasn't changed and that you've remedied all calling convention violations.

Once you have fixed everything, running the above Venus command should output the following:

Sanity checks passed! Make sure there are no CC violations.
Found 0 warnings!

Checkoff

Exercise 2: RISC-V function calling with map

This exercise uses the file ex2.s.

In this exercise, you will complete an implementation of map on linked-lists in RISC-V. Our function will be simplified to mutate the list in-place, rather than creating and returning a new list with the modified values.

You will find it helpful to refer to the RISC-V green card to complete this exercise. If you encounter any instructions or pseudo-instructions you are unfamiliar with, use this as a resource.

Our map procedure will take two parameters; the first parameter will be the address of the head node of a singly-linked list whose values are 32-bit integers. So, in C, the structure would be defined as:

struct node {
    int value;
    struct node *next;
};

Our second parameter will be the address of a function that takes one int as an argument and returns an int. We'll use the jalr RISC-V instruction to call this function on the list node values.

Our map function will recursively go down the list, applying the function to each value of the list and storing the value returned in that corresponding node. In C, the function would be something like this:

void map(struct node *head, int (*f)(int))
{
    if (!head) { return; }
    head->value = f(head->value);
    map(head->next,f);
}

If you haven't seen the int (*f)(int) kind of declaration before, don't worry too much about it. Basically it means that f is a pointer to a function that takes an int as an argument. We can call this function f just like any other.

There are exactly ten (10) markers (1 in done, 7 in map, and 2 in main) in the provided code where it says TODO: YOUR CODE HERE.

Action Item

Complete the implementation of map by filling out each of these ten markers with the appropriate code. Furthermore, provide a call to map with square as the function argument. There are comments in the code that explain what should be accomplished at each marker. When you've filled in these instructions, running the code should provide you with the following output:

9 8 7 6 5 4 3 2 1 0 
81 64 49 36 25 16 9 4 1 0 
80 63 48 35 24 15 8 3 0 -1 

The first line is the original list, and the second line is the list with all elements squared after calling map(head, &square), and the third is the list with all elements decremented after now calling map(head, &decrement).

Checkoff

Exercise 3: implementing bitwise reverse operation in two ways

This exercise uses the file ex3.s.

Write two versions of a bitwise reverse function that, given a value in a0, returns the value in a0 with the bits reversed, e.g. 01011101 to 10111010.

When you've finished the two functions, running the code should provide you with the following output:

0x12345678 0x1E6A2C48 0x1E6A2C48 0x1E6A2C48 
0x71C924BF 0xFD24938E 0xFD24938E 0xFD24938E 
0x19260817 0xE8106498 0xE8106498 0xE8106498 

Checkoff